﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Text.RegularExpressions;
using System.Web.Configuration;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using Microsoft.VisualBasic.Devices;
using Microsoft.VisualBasic.MyServices;

namespace ImpEx.Engine
{
    public class SPSBackup
    {
        public class FileCollection : List<string>
        {
        }

        public class AssemblyList : Dictionary<string, FileCollection>
        {
        }

        private static List<string> _allowedFileTypes = new List<string>(new[]
        {
            ".webpart", ".xml", ".aspx", ".ascx", ".master", ".config"
        });

        public static void Execute()
        {
            #if FULLVERSION

            Logger.WriteLine("Welcome to SharePoint Server FileSystem Content Backup");
            Logger.WriteLine("Starting...");

            string wssPath = @"c:\Program Files\Common Files\Microsoft Shared\web server extensions\12\";

            string sspPath = wssPath + @"TEMPLATE";
            AssemblyList assemblies = new AssemblyList();

            FileSystemProxy fsp = new Computer().FileSystem;

            if (Directory.Exists("backup"))
                Directory.Delete("backup", true);

            Directory.CreateDirectory("backup");
            Directory.CreateDirectory("backup\\gac");
            Directory.CreateDirectory("backup\\12\\template");
            Directory.CreateDirectory("backup\\12\\template\\features");
            Directory.CreateDirectory("backup\\solutions");
            Directory.CreateDirectory("backup\\inetpub");

            Dictionary<Guid, SPFeature> farmFeatures = new Dictionary<Guid, SPFeature>();

            Logger.WriteLine("Backup installed solutions");

            foreach (SPSolution solution in SPFarm.Local.Solutions)
            {
                Logger.WriteLine(" - " + solution.DisplayName);
                SPPersistedFile wspFile = solution.SolutionFile;
                string filename = "backup\\solutions\\" + wspFile.Name;

                string outputFileName = filename;
                int index = 0;
                while (File.Exists(outputFileName))
                {
                    outputFileName =
                        string.Format("{0}[{1}]{2}",
                                      Path.GetFileName(filename), ++index, Path.GetExtension(filename));
                }
                wspFile.SaveAs(filename);
            }

            Logger.WriteLine("Collect webapplication");
            List<SPWebApplication> webApps = new List<SPWebApplication>();
            //foreach (SPWebApplication app in new SPWebApplicationCollection(SPWebService.AdministrationService))
            //    webApps.Add(app);

            foreach (SPWebApplication app in new SPWebApplicationCollection(SPWebService.ContentService))
            {
                webApps.Add(app);
            }

            foreach (SPWebApplication webapp in webApps)
            {
                Logger.WriteLine("Process webapplication: " + webapp.Name);

                string configFile = webapp.IisSettings[0].Path.FullName + "\\web.config";
                ExeConfigurationFileMap fileMap = new ExeConfigurationFileMap();
                fileMap.ExeConfigFilename = configFile;

                Configuration config = ConfigurationManager.OpenMappedExeConfiguration(
                    fileMap, ConfigurationUserLevel.None);

                Logger.WriteLine("process inetpub");
                string appPath = Path.GetDirectoryName(config.FilePath);
                ProcessFileType(appPath, "web.config", assemblies);

                fsp.CopyDirectory(appPath, "backup\\inetpub\\" + Path.GetFileName(appPath));

                SPFeatureCollection features = webapp.Features;
                Logger.WriteLine("- process features webapplication level");
                ProcessFeatures(farmFeatures, features);
                foreach (SPSite site in webapp.Sites)
                {
                    Logger.WriteLine("- process features site level");
                    ProcessFeatures(farmFeatures, site.Features);
                    Logger.WriteLine("- process features web level");
                    foreach (SPWeb web in site.AllWebs)
                    {
                        ProcessFeatures(farmFeatures, web.Features);
                        ProcessSPFolder(web.RootFolder, assemblies);
                    }
                }
                Logger.WriteLine("");
            }

            ProcessFileType(sspPath, "*.webpart", assemblies);
            ProcessFileType(sspPath, "*.xml", assemblies);
            ProcessFileType(sspPath, "*.aspx", assemblies);
            ProcessFileType(sspPath, "*.ascx", assemblies);
            ProcessFileType(sspPath, "*.master", assemblies);
            ProcessFileType(sspPath, "*.config", assemblies);


            foreach (string assemblyName in assemblies.Keys)
            {
                Assembly a;
                try
                {
                    a = Assembly.Load(assemblyName);
                }
                catch (Exception)
                {
                    continue;
                }

                string path = a.Location;
                string fname = "backup\\gac\\" + Path.GetFileName(a.Location);

                File.Copy(a.Location, fname, true);

                foreach (string file in assemblies[assemblyName])
                {
                    if (file.StartsWith(wssPath))
                    {
                        string dest = file.Remove(0, wssPath.Length);
                        if (dest.ToLowerInvariant().StartsWith("template\\features\\"))
                        {
                            string source = Path.GetDirectoryName(file);
                            string destin = "backup\\12\\" + Path.GetDirectoryName(dest);
                            fsp.CopyDirectory(source, destin, true);
                        }
                        else
                        {
                            fsp.CopyFile(file, "backup\\12\\" + dest, true);
                        }
                    }
                }
            }
            Logger.WriteLine("Backup completed.");
            #endif
        }

        private static void ProcessSPFolder(SPFolder folder, AssemblyList assemblyList)
        {
            Logger.WriteLine("folder: " + folder.Name + folder.Url);
            foreach (SPFile file in folder.Files)
            {
                if (CanProcessFile(file.Name))
                {
                    try
                    {
                        using (Stream fs = file.OpenBinaryStream())
                        {
                            using (StreamReader reader = new StreamReader(fs))
                            {
                                Logger.WriteLine("file: " + file.Url);
                                ProcessFile(file.Url, reader, assemblyList);
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        Logger.WriteLine("Exception: " + e.Message);
                    }
                }
            }
            foreach (SPFolder child in folder.SubFolders)
            {
                ProcessSPFolder(child, assemblyList);
            }
        }

        private static void ProcessFeatures(Dictionary<Guid, SPFeature> farmFeatures, SPFeatureCollection features)
        {
            #if FULLVERSION
            foreach (SPFeature feature in features)
            {
                if (feature.Definition != null && !farmFeatures.ContainsKey(feature.Definition.Id))
                {
                    farmFeatures.Add(feature.Definition.Id, feature);
                    Logger.WriteLine(feature.Definition.GetTitle(CultureInfo.InvariantCulture));
                }
            }
            #endif
        }

        private static void ProcessFile(string file, AssemblyList assemblies)
        {
            Console.WriteLine(file);

            using (StreamReader reader = new StreamReader(file))
            {
                ProcessFile(file, reader, assemblies);
            }
        }

        private static void ProcessFile(string file, StreamReader reader, AssemblyList assemblies)
        {
            ProcessFileStream(reader, delegate(string libName)
            {
                if (!assemblies.ContainsKey(libName))
                {
                    assemblies.Add(libName, new FileCollection());
                }

                if (!assemblies[libName].Contains(file.ToLowerInvariant()))
                {
                    assemblies[libName].Add(file.ToLowerInvariant());
                }
            });
        }


            #if FULLVERSION
            private static Regex[] regexs =
            {
                new Regex(
                    @"(inherits|<type name)=""[^""]+?,([^""]+?, version=.*?, culture=.*?, publickeytoken=.*?)""",
                    RegexOptions.Compiled | RegexOptions.IgnoreCase),

                new Regex(
                    @"(assembly|assembly[ ]name)=""([^""]+?, version=.*?, culture=.*?, publickeytoken=.*?)""",
                    RegexOptions.Compiled | RegexOptions.IgnoreCase),

                new Regex(
                    @"<Assembly>(.+?, version=.+?, culture=.+?, publickeytoken=.+?)</Assembly>",
                    RegexOptions.Compiled | RegexOptions.IgnoreCase)
            };
            #endif

        internal delegate void OnAssemblyFound(string libName);

        internal static void ProcessFileStream(StreamReader reader, OnAssemblyFound fOnLibrary)
        {
                #if FULLVERSION
                string data = reader.ReadToEnd();
                data = data.Replace('\t', ' ').Replace("\r\n", "").Replace("\r", "").Replace("\n", "")
                    .Replace(",", ", ");

                int length = data.Length;
                while (true)
                {
                    data = data.Replace("  ", " ");
                    if (data.Length == length) break;
                    length = data.Length;
                }

                LookupAssembly(fOnLibrary, data, regexs[0], 2);
                LookupAssembly(fOnLibrary, data, regexs[1], 2);
                LookupAssembly(fOnLibrary, data, regexs[2], 1);
                #endif
        }

        public static bool CanProcessFile(string fileName)
        {
            string ext = Path.GetExtension(fileName.ToLowerInvariant());
            return (_allowedFileTypes.Contains(ext));
        }

        private static void LookupAssembly(OnAssemblyFound fOnLibrary, string data, Regex regex, int group)
        {
            #if FULLVERSION
            MatchCollection matches = regex.Matches(data);
            foreach (Match match in matches)
            {
                string assembly = match.Groups[group].Value.Trim().ToLowerInvariant();
                {
                    fOnLibrary(assembly);
                }
            }
            #endif
        }

        public static void ProcessFileType(string path, string pattern, AssemblyList assemblies)
        {
            string[] files = Directory.GetFiles(path, pattern, SearchOption.AllDirectories);
            foreach (string file in files)
            {
                ProcessFile(file, assemblies);
            }
        }
    }
}